/*file chiron src/crypt/otp.js */
/*preamble

    Copyright (c) 2002-2008 Kris Kowal <http://cixar.com/~kris.kowal>
    MIT License
    
    The license terms are stated in full in <license.rst> and at the end
    of all source files.

*/

/**
    random one-time pad generator
*/

/*status todo */

/*

    L'Ecuyer's two-sequence generator with a Bays-Durham shuffle
    on the back-end.  Schrage's algorithm is used to perform
    64-bit modular arithmetic within the 32-bit constraints of
    JavaScript.

    Bays, C. and S. D. Durham.  ACM Trans. Math. Software: 2 (1976)
        59-64.

    L'Ecuyer, P.  Communications of the ACM: 31 (1968) 742-774.

    Schrage, L.  ACM Trans. Math. Software: 5 (1979) 132-138.

*/

function uGen(old, a, q, r, m) {      // Schrage's modular multiplication algorithm
    var t;

    t = Math.floor(old / q);
    t = a * (old - (t * q)) - (t * r);
    return Math.round((t < 0) ? (t + m) : t);
}

function LEnext() {                   // Return next raw value
    var i;

    this.gen1 = uGen(this.gen1, 40014, 53668, 12211, 2147483563);
    this.gen2 = uGen(this.gen2, 40692, 52774, 3791, 2147483399);

    /* Extract shuffle table index from most significant part
       of the previous result. */

    i = Math.floor(this.state / 67108862);

    // New state is sum of generators modulo one of their moduli

    this.state = Math.round((this.shuffle[i] + this.gen2) % 2147483563);

    // Replace value in shuffle table with generator 1 result

    this.shuffle[i] = this.gen1;

    return this.state;
}

//  Return next random integer between 0 and n inclusive

function LEnint(n) {
    return Math.floor(this.next() / (1 + 2147483562 / (n + 1)));
}

//  Constructor.  Called with seed value

function LEcuyer(s) {
    var i;

    this.shuffle = new Array(32);
    this.gen1 = this.gen2 = (s & 0x7FFFFFFF);
    for (i = 0; i < 19; i++) {
        this.gen1 = uGen(this.gen1, 40014, 53668, 12211, 2147483563);
    }

    // Fill the shuffle table with values

    for (i = 0; i < 32; i++) {
        this.gen1 = uGen(this.gen1, 40014, 53668, 12211, 2147483563);
        this.shuffle[31 - i] = this.gen1;
    }
    this.state = this.shuffle[0];
    this.next = LEnext;
    this.nextInt = LEnint;
}

function sepchar() {
    if (rsep) {
        var seps = "!#$%&()*+,-./:;<=>?@[]^_{|}~";
        return seps.charAt(sepran.nextInt(seps.length - 1));
    }
    return "-";
}

function gen() {
    window.status = "Generating...";
    document.getElementById('onetime').pad.value = "";

    lower = document.getElementById('onetime').textcase.selectedIndex == 0;
    upper = document.getElementById('onetime').textcase.selectedIndex == 1;
    mixed = document.getElementById('onetime').textcase.selectedIndex == 2;
    rsep = document.getElementById('onetime').rsep.checked;
    if (!(numeric = document.getElementById('onetime').keytype[0].checked)) {
        english = document.getElementById('onetime').keytype[1].checked;
        gibberish = document.getElementById('onetime').keytype[3].checked;
    }
    clockseed = document.getElementById('onetime').seedy[0].checked
    makesig = document.getElementById('onetime').dosig.checked;
    npass = document.getElementById('onetime').nkeys.value;
    pw_length = Math.round(document.getElementById('onetime').klength.value);
    sep = document.getElementById('onetime').sep.value;
    linelen = document.getElementById('onetime').linelen.value;
//               01234567890123456789012345678901
    charcodes = " " +
                "!\"#$%&'()*+,-./0123456789:;<=>?" +
                "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_" +
                "`abcdefghijklmnopqrstuvwxyz{|}~";

    if (clockseed) {
        var n, j, ran0;

        /*  Obtain seed from the clock.  To reduce the likelihood
            of the seed being guessed, we create the seed by combining
            the time of the request with the time the page was loaded,
            then use that composite value to seed an auxiliary generator
            which is cycled between one and 32 times based on the time
            derived initial seed, with the output of the generator fed
            back into the seed we use to generate the pad.  */

        seed = Math.round((new Date()).getTime() % Math.pow(2, 31));
        ran0 = new LEcuyer((seed ^ Math.round(loadTime % Math.pow(2, 31))) & 0x7FFFFFFF);
        for (j = 0; j < (5 + ((seed >> 3) & 0xF)); j++) {
            n = ran0.nextInt(31);
        }
        while (n-- >= 0) {
            seed = ((seed << 11) | (seed >>> (32 - 11))) ^ ran0.next();
        }
        seed &= 0x7FFFFFFF;
        document.getElementById('onetime').seeder.value = seed;
    } else {
        var useed, seedNum;

        /* Obtain seed from user specification.  If the seed is a
           decimal number, use it as-is.  If it contains any
           non-numeric characters, construct a hash code and
           use that as the seed. */

        useed = document.getElementById('onetime').seeder.value;
        seedNum = true;
        for (i = 0; i < useed.length; i++) {
            if ("0123456789".indexOf(useed.charAt(i)) == -1) {
                seedNum = false;
                break;
            }
        }
        if (seedNum) {
            seed = Math.round(Math.floor(document.getElementById('onetime').seeder.value) % Math.pow(2, 31));
            document.getElementById('onetime').seeder.value = seed;
        } else {
            var s, t, iso, hex;

            iso = "";
            hex = "0123456789ABCDEF";
            for (i = 32; i < 256; i++) {
                if (i < 127 || i >= 160) {
                    // Why not "s = i.toString(16);"?  Doesn't work in Netscape 3.0
                    iso += "%" + hex.charAt(i >> 4) + hex.charAt(i & 0xF);
                }
            }
            iso = unescape(iso);
            s = 0;
            for (i = 0; i < useed.length; i++) {
                t = iso.indexOf(useed.charAt(i));
                if (t < 0) {
                    t = 17;
                }
                s = 0x7FFFFFFF & (((s << 5) | (s >> (32 - 5))) ^ t);
            }
            seed = s;
        }
    }
    ran1 = new LEcuyer(seed);
    ran2 = new LEcuyer(seed);
    if (rsep) {
        /*  Use a separate random generator for separators
            so that results are the same for a given seed
            for both choices of separators.  */
        sepran = new LEcuyer(seed);
    }

    ndig = 1;
    j = 10;
    while (npass >= j) {
        ndig++;
        j *= 10;
    }
    pw_item = pw_length + (sep > 0 ? (pw_length / sep) : 0);
    pw_item += ndig + 5;
    j = pw_item * 3;
    if (j < 132) {
        j = 132;
    }
    npline = Math.floor(linelen / pw_item);
    if (npline < 1) {
        npline = 0;
    }
    v = "";
    md5v = "";
    lineno = 0;
    if (!numeric) {
        letters = "abcdefghijklmnopqrstuvwxyz";
        if (upper) {
            letters = letters.toUpperCase();
        }
        if (english) {

            // Frequency of English digraphs (from D. Edwards 1/27/66)

            frequency = new Array( 
                new Array(4, 20, 28, 52, 2, 11, 28, 4, 32, 4, 6, 62,
                          23, 167, 2, 14, 0, 83, 76, 127, 7, 25, 8, 1,
                          9, 1), /* aa - az */

                new Array(13, 0, 0, 0, 55, 0, 0, 0, 8, 2, 0, 22, 0, 0,
                          11, 0, 0, 15, 4, 2, 13, 0, 0, 0, 15, 0), /* ba - bz */

                new Array(32, 0, 7, 1, 69, 0, 0, 33, 17, 0, 10, 9, 1,
                          0, 50, 3, 0, 10, 0, 28, 11, 0, 0, 0, 3, 0), /* ca - cz */

                new Array(40, 16, 9, 5, 65, 18, 3, 9, 56, 0, 1, 4, 15,
                          6, 16, 4, 0, 21, 18, 53, 19, 5, 15, 0, 3, 0), /* da - dz */

                new Array(84, 20, 55, 125, 51, 40, 19, 16, 50, 1, 4,
                          55, 54, 146, 35, 37, 6, 191, 149, 65, 9, 26,
                          21, 12, 5, 0), /* ea - ez */

                new Array(19, 3, 5, 1, 19, 21, 1, 3, 30, 2, 0, 11, 1,
                          0, 51, 0, 0, 26, 8, 47, 6, 3, 3, 0, 2, 0), /* fa - fz */

                new Array(20, 4, 3, 2, 35, 1, 3, 15, 18, 0, 0, 5, 1,
                          4, 21, 1, 1, 20, 9, 21, 9, 0, 5, 0, 1, 0), /* ga - gz */

                new Array(101, 1, 3, 0, 270, 5, 1, 6, 57, 0, 0, 0, 3,
                          2, 44, 1, 0, 3, 10, 18, 6, 0, 5, 0, 3, 0), /* ha - hz */

                new Array(40, 7, 51, 23, 25, 9, 11, 3, 0, 0, 2, 38,
                          25, 202, 56, 12, 1, 46, 79, 117, 1, 22, 0,
                          4, 0, 3), /* ia - iz */

                new Array(3, 0, 0, 0, 5, 0, 0, 0, 1, 0, 0, 0, 0, 0, 4,
                          0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0), /* ja - jz */

                new Array(1, 0, 0, 0, 11, 0, 0, 0, 13, 0, 0, 0, 0, 2,
                          0, 0, 0, 0, 6, 2, 1, 0, 2, 0, 1, 0), /* ka - kz */

                new Array(44, 2, 5, 12, 62, 7, 5, 2, 42, 1, 1, 53, 2,
                          2, 25, 1, 1, 2, 16, 23, 9, 0, 1, 0, 33, 0), /* la - lz */

                new Array(52, 14, 1, 0, 64, 0, 0, 3, 37, 0, 0, 0, 7,
                          1, 17, 18, 1, 2, 12, 3, 8, 0, 1, 0, 2, 0), /* ma - mz */

                new Array(42, 10, 47, 122, 63, 19, 106, 12, 30, 1, 6,
                          6, 9, 7, 54, 7, 1, 7, 44, 124, 6, 1, 15, 0,
                          12, 0), /* na - nz */

                new Array(7, 12, 14, 17, 5, 95, 3, 5, 14, 0, 0, 19,
                          41, 134, 13, 23, 0, 91, 23, 42, 55, 16, 28,
                          0, 4, 1), /* oa - oz */

                new Array(19, 1, 0, 0, 37, 0, 0, 4, 8, 0, 0, 15, 1, 0,
                          27, 9, 0, 33, 14, 7, 6, 0, 0, 0, 0, 0), /* pa - pz */

                new Array(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                          0, 0, 0, 0, 0, 17, 0, 0, 0, 0, 0), /* qa - qz */

                new Array(83, 8, 16, 23, 169, 4, 8, 8, 77, 1, 10, 5,
                          26, 16, 60, 4, 0, 24, 37, 55, 6, 11, 4, 0,
                          28, 0), /* ra - rz */

                new Array(65, 9, 17, 9, 73, 13, 1, 47, 75, 3, 0, 7,
                          11, 12, 56, 17, 6, 9, 48, 116, 35, 1, 28, 0,
                          4, 0), /* sa - sz */

                new Array(57, 22, 3, 1, 76, 5, 2, 330, 126, 1, 0, 14,
                          10, 6, 79, 7, 0, 49, 50, 56, 21, 2, 27, 0,
                          24, 0), /* ta - tz */

                new Array(11, 5, 9, 6, 9, 1, 6, 0, 9, 0, 1, 19, 5, 31,
                          1, 15, 0, 47, 39, 31, 0, 3, 0, 0, 0, 0), /* ua - uz */

                new Array(7, 0, 0, 0, 72, 0, 0, 0, 28, 0, 0, 0, 0, 0,
                          5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0), /* va - vz */

                new Array(36, 1, 1, 0, 38, 0, 0, 33, 36, 0, 0, 4, 1,
                          8, 15, 0, 0, 0, 4, 2, 0, 0, 1, 0, 0, 0), /* wa - wz */

                new Array(1, 0, 2, 0, 0, 1, 0, 0, 3, 0, 0, 0, 0, 0, 1,
                          5, 0, 0, 0, 3, 0, 0, 1, 0, 0, 0), /* xa - xz */

                new Array(14, 5, 4, 2, 7, 12, 12, 6, 10, 0, 0, 3, 7,
                          5, 17, 3, 0, 4, 16, 30, 0, 0, 5, 0, 0, 0), /* ya - yz */

                new Array(1, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                          0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) /* za - zz */ );

            // This MUST be equal to the sum of the equivalent rows above.

            row_sums = new Array(
                796,   160,    284,    401,    1276,   262,    199,    539,    777,    
                16,    39,     351,    243,    751,    662,    181,    17,     683,    
                662,   968,    248,    115,    180,    17,     162,    5
            );

            // Frequencies of starting characters.

            start_freq = new Array(
                1299,  425,    725,    271,    375,    470,    93,     223,    1009,
                24,    20,     355,    379,    319,    823,    618,    21,     317,
                962,   1991,   271,    104,    516,    6,      16,     14
            );

            // This MUST be equal to the sum of all elements in the above array.

            total_sum = 11646;
        }
        if (gibberish) {
            gibber = "abcdefghijklmnopqrstuvwxyz" +
                     "ABCDEFGHIJKLMNOPQRSTUVWXYZ" +
                     "0123456789" +
                     "!#$%&()*+,-./:;<=>?@[]^_{|}~";
            if (upper) {
                /*  Convert to upper case, leaving two copies of the
                    alphabet for two reasons: first, to favour letters
                    over gnarl, and second, to change only the letter case
                    when the mode is selected.  */
                gibber = gibber.toUpperCase();
            } else if (lower) {
            gibber = gibber.toLowerCase();
        }
        }
    }
    for (line = 1; line <= npass; line++) {
        password = "";
        if (numeric) {
            for (nchars = 0; nchars < pw_length; nchars++) {
                if ((sep > 0) && ((nchars % sep) == 0) && (nchars > 0)) {
                    password += sepchar();
                }
                password += ran1.nextInt(9);
            }
        } else if (!english) {
            for (nchars = 0; nchars < pw_length; nchars++) {
                if ((sep > 0) && ((nchars % sep) == 0) && (nchars > 0)) {
                    password += sepchar();
                }
                if (gibberish) {
                    password += gibber.charAt(ran1.nextInt(gibber.length - 1));
                } else {
                    password += letters.charAt(ran1.nextInt(25));
                }
            }
        } else {
            position = ran1.nextInt(total_sum - 1);
            for (row_position = 0, j = 0; position >= row_position;
                 row_position += start_freq[j], j++) {
                continue;
            }

            password = letters.charAt(i = j - 1);
            nch = 1;
            for (nchars = pw_length - 1; nchars; --nchars) {

                // Now find random position within the row.

                position = ran1.nextInt(row_sums[i] - 1);
                for (row_position = 0, j = 0;
                     position >= row_position;
                     row_position += frequency[i][j], j++) {
                }

                if ((sep > 0) && ((nch % sep) == 0)) {
                    password += sepchar();
                }
                nch++;
                password += letters.charAt(i = j - 1);
            }
        }
    
    if ((!numeric) && (!gibberish) && mixed) {
        var pwm = '';
        var j;
        for (j = 0; j < password.length; j++) {
            pwm += ran2.nextInt(1) ? (password.charAt(j)) : (password.charAt(j).toUpperCase());
        }
        password = pwm;
    }

        /*  If requested, calculate the MD5 signature for this key and
            and save for later appending to the results.  */

        if (makesig) {
            var n, m, hex = "0123456789ABCDEF";

            init();
            for (m = 0; m < password.length; m++) {
                update(32 + charcodes.indexOf(password.charAt(m)));
            }
            finish();

            for (n = 0; n < 16; n++) {
                md5v += hex.charAt(digestBits[n] >> 4);
                md5v += hex.charAt(digestBits[n] & 0xF);
            }
            md5v += "\n";
        }

        aline = "" + line;
        while (aline.length < ndig) {
            aline = " " + aline;
        }
        v += aline + ") " + password;

        if ((++lineno) >= npline) {
            v += "\n";
            lineno = 0;
        } else {
            v += "  ";
        }
    }

    if (makesig) {
        v += "\n----------  MD5 Signatures  ----------\n" + md5v;
    }

    document.getElementById('onetime').pad.value = v;
    window.status = "Done.";
}


/*license

    Legal
    =======
    
    Chiron is a component of the Tale web-game project.
    
    See <credit.txt> for a complete list of
    contributions and their licenses.  All contributions are provided
    under permissive, non-viral licenses including MIT, BSD, Creative Commons
    Attribution 2.5, Public Domain, or Unrestricted.
    
    
    License
    =======
    
    Copyright (c) 2002-2008 Kris Kowal <http://cixar.com/~kris.kowal>
    MIT License
    
    
    MIT License
    -----------
    
    Permission is hereby granted, free of charge, to any person
    obtaining a copy of this software and associated documentation
    files (the "Software"), to deal in the Software without
    restriction, including without limitation the rights to use,
    copy, modify, merge, publish, distribute, sublicense, and/or sell
    copies of the Software, and to permit persons to whom the
    Software is furnished to do so, subject to the following
    conditions:
    
    The above copyright notice and this permission notice shall be
    included in all copies or substantial portions of the Software.
    
    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
    EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
    OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
    NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
    HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
    WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
    FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
    OTHER DEALINGS IN THE SOFTWARE.

*/

